跳到主要内容

C# 反射创建对象

参考资料 菜鸟教程 C# 反射(Reflection)

C# 反射(Reflection)

反射指程序可以访问、检测和修改它本身状态或行为的一种能力。

程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。

可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。

它的缺点:

1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

typeof 关键字

typeof 运算符用于获取某个类型的 System.Type 实例。 typeof 运算符的实参必须是类型或类型形参的名称

// 这里定义一个方法(这个语法是 C# 的 Lambda)
void PrintType<T>() => Console.WriteLine(typeof(T));

Console.WriteLine(typeof(List<string>));
PrintType<int>();
PrintType<System.Int32>();
PrintType<Dictionary<int, char>>();
// Output:
// System.Collections.Generic.List`1[System.String]
// System.Int32
// System.Int32
// System.Collections.Generic.Dictionary`2[System.Int32,System.Char]

简单使用例

参考资料 C# 反射,通过类名、方法名调用方法

因为 C# 中一个命名空间可以在不同的文件里面,而且 C# 不像 Java 那样使用文件名称来约束类名,从而可以直接全类名创建对象,所以需要使用 命名空间 + 类名 来创建,不然会找不到类。

// 这里的 StudyCSharp.MyTestClassNamespace 就是命名空间  TestClass02 就是类名
// 注意,要给这个类一个无参构造,且别忘了用 public 修饰
string nspace = "StudyCSharp.MyTestClassNamespace.TestClass02";
Activator.CreateInstance(Type.GetType(nspace));

下面是具体实例

using System;
using System.Reflection;

class Test
{
// 无参数,无返回值方法
public void Method()
{
Console.WriteLine("Method(无参数) 调用成功!");
}

// 有参数,无返回值方法
public void Method(string str)
{
Console.WriteLine("Method(有参数) 调用成功!参数 :" + str);
}

// 有参数,有返回值方法
public string Method(string str1, string str2)
{
Console.WriteLine("Method(有参数,有返回值) 调用成功!参数 :" + str1 + ", " + str2);
string className = this.GetType().FullName; // 非静态方法获取类名
return className;
}
}

class Program
{
static void Main(string[] args)
{
string strClass = "Test"; // 命名空间+类名
string strMethod = "Method"; // 方法名

Type type; // 存储类
Object obj; // 存储类的实例

type = Type.GetType(strClass); // 通过类名获取同名类
obj = System.Activator.CreateInstance(type); // 创建实例

MethodInfo method = type.GetMethod(strMethod, new Type[] {}); // 获取方法信息
object[] parameters = null;
method.Invoke(obj, parameters); // 调用方法,参数为空

// 注意获取重载方法,需要指定参数类型
method = type.GetMethod(strMethod, new Type[] { typeof(string) }); // 获取方法信息
parameters = new object[] {"hello"};
method.Invoke(obj, parameters); // 调用方法,有参数

method = type.GetMethod(strMethod, new Type[] { typeof(string), typeof(string) }); // 获取方法信息
parameters = new object[] { "hello", "你好" };
string result = (string)method.Invoke(obj, parameters); // 调用方法,有参数,有返回值
Console.WriteLine("Method 返回值:" + result); // 输出返回值

// 获取静态方法类名
string className = MethodBase.GetCurrentMethod().ReflectedType.FullName;
Console.WriteLine("当前静态方法类名:" + className);

Console.ReadKey();
}
}

查看元数据

使用反射(Reflection)可以查看特性(attribute)信息。

System.Reflection 类的 MemberInfo 对象需要被初始化,用于发现与类相关的特性(attribute)。为了做到这点,可以定义目标类的一个对象,如下:

System.Reflection.MemberInfo info = typeof(MyClass);

下面的程序演示了这点:

using System;

[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
public readonly string Url;

// Topic 是一个命名(named)参数
public string Topic {get; set;}

public HelpAttribute(string url) // url 是一个定位(positional)参数
{
this.Url = url;
}
}

[HelpAttribute("Information on the class MyClass")]
class MyClass
{
}

namespace AttributeAppl
{
class Program
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);
for (int i = 0; i < attributes.Length; i++)
{
System.Console.WriteLine(attributes[i]);
}
Console.ReadKey();

}
}
}

当上面的代码被编译和执行时,它会显示附加到类 MyClass 上的自定义特性:

HelpAttribute

读取命名空间下的类

namespace StudyCSharp
{
class Program
{
static void Main(string[] args)
{
string nspace = "StudyCSharp.MyTestClassNamespace";

var q = from t in Assembly.GetExecutingAssembly().GetTypes()
where t.IsClass && t.Namespace == nspace
select t;

q.ToList().ForEach(t =>
{
Console.WriteLine("类名称为:" + t.Name);
// 取得全部构造方法(注意,这里只显示 public 修饰的)
foreach (ConstructorInfo info in t.GetConstructors())
{
Console.WriteLine("构造方法参数:");
// 打印参数
foreach (var parameter in info.GetParameters())
{
Console.Write(" 参数名称为:" + parameter.Name );
Console.Write(" 参数类型为:" + parameter.ParameterType);
}
Console.Write("\n");
}
Console.WriteLine("\n");
});
}
}

// 用来测试读取类
namespace MyTestClassNamespace
{
abstract class BaseTestClass
{
public abstract void printName();
}

class TestClass01 : BaseTestClass
{
public TestClass01(int age, string name)
{
// 这里随便创建一个和其它派生类不同的构造函数
}

public TestClass01(int age)
{
// 这里随便创建一个和其它派生类不同的构造函数
}

public override void printName()
{
Console.WriteLine("这是 TestClass01");
}
}

class TestClass02 : BaseTestClass
{
// 这里没有用 public 修饰,所以不显示
TestClass02(bool flag)
{
}

public override void printName()
{
Console.WriteLine("这是 TestClass02");
}
}
}
}

打印的结果为:

类名称为:BaseTestClass


类名称为:TestClass01
构造方法参数:
参数名称为:age 参数类型为:System.Int32 参数名称为:name 参数类型为:System.String
构造方法参数:
参数名称为:age 参数类型为:System.Int32


类名称为:TestClass02

取得继承基类的所有子类

static class ReflectionHelper
{
public static IEnumerable<T> CreateAllInstancesOf<T>()
{
return typeof (ReflectionHelper).Assembly.GetTypes() //获取当前类库下所有类型
.Where(t => typeof (T).IsAssignableFrom(t)) //获取间接或直接继承t的所有类型
.Where(t => !t.IsAbstract && t.IsClass) //获取非抽象类 排除接口继承
.Select(t => (T) Activator.CreateInstance(t)); //创造实例,并返回结果(项目需求,可删除)
}
}

使用带参的构造函数

参考资料 反射之动态创建对象 参考资料 C# 利用反射动态创建对象——带参数的构造函数和String类型

“反射”其实就是利用程序集的元数据信息。

反射可以有很多方法,编写程序时请先导入 System.Reflection 命名空间,假设你要反射一个 DLL 中的类,并且没有引用它(即未知的类型):

Assembly assembly = Assembly.LoadFile("程序集路径,不能是相对路径"); // 加载程序集(EXE 或 DLL) 
object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)"); // 创建类的实例

若要反射当前项目中的类可以为:

Assembly assembly = Assembly.GetExecutingAssembly(); // 获取当前程序集 
object obj = assembly.CreateInstance("类的完全限定名(即包括命名空间)"); // 创建类的实例,返回为 object 类型,需要强制类型转换

也可以为:

Type type = Type.GetType("类的完全限定名"); 
object obj = type.Assembly.CreateInstance(type);

上述描述中提到的三种方法其实都是大同小异的,核心就是通过 System.Reflection.Assembly 类型的 CreateInstance 方法创建实例。

那么简单的解释一下这种方法的原理:

  1. 找到要实例化的类所在的程序集,并将之实例为 System.Reflection.Assembly 类的对象
  2. 利用 System.Reflection.Assembly 类提供的 CreateInstance 方法,创建类的对象

但是上面那几种方法都只能创建无参构造函数

这里创建一个带参测试类

class Test
{
private string _strId;
public string ID
{
get { return _strId; }
set { _strId = value; }
}

public Test(string str)
{
_strId = str;
}
}

可以直接通过取得类型的 ConstructorInfo 来创建对象

// 使用的类是上面读取命名空间那节创建的类

static void Main(string[] args)
{
string nspace = "StudyCSharp.MyTestClassNamespace.TestClass01";
var mytype = Type.GetType(nspace);
ConstructorInfo[] gc = mytype.GetConstructors();
ConstructorInfo constructor = mytype.GetConstructor(new Type[2] {typeof(int), typeof(string)});
// 通过形参的数量,类型来取得不同的构造函数
//ConstructorInfo constructor = mytype.GetConstructor(new Type[1] {typeof(int)});
var obj = (BaseTestClass)constructor.Invoke(new object[2] {18, "张三"});
obj.printName();
}

如上所示,这种方式对参数的排序要求很高

总结:利用反射创建对象两种方法

1、利用 Activator.CreateInstance,前提是调用对象的默认无参构造函数

2、利用构造器来动态创建对象

dynamic 关键字

参考资料 探索C# dynamic关键字 (1) - 简介

上面的例子可以使用 dynamic 关键字继续优化

static void Main(string[] args)
{
/* ...... */

ConstructorInfo constructor = mytype.GetConstructor(new Type[2] {typeof(int), typeof(string)});
dynamic obj = constructor.Invoke(new object[2] {18, "张三"});
obj.printName();
}

可以发现无需再强转了,那这个神奇的关键字是什么呢?

C#4.0 引入了一个新类型 dynamic。该类型是一种静态类型,但类型为 dynamic 的对象会跳过静态类型检查。

它指示动态类型。我们可以直接创建一个 dynamic 类型的变量,可以将任意对象赋值给它。如下所示:

dynamic dynVar1 = 1;
dynamic dynVar2 = new Object();

当我们在代码中使用了 dynamic 类型时,就是在告诉编译器关闭对该对象的运行时检查,而在运行时确定对象类型。比如,以下代码可以成功编译:

dynamic numericDyn = 80;
numericDyn.Greet();

但是在这段代码会抛出一个运行时异常 RuntimeBinderException,提示我们类型 int 不包含方法 Greet() 的定义。这说明了在底层, dynamic 变量类型在运行时仍然是确定的,也就是说,C#依然是静态类型化语言。当我们在一个 dynamic 变量上调用 GetType() 时,会输出该对象的实际类型。如下所示:

Console.WriteLine(numericDyn.GetType());
// Console prints System.Int32

var, object 和 dynamic

关键字 var 用于编译时类型推断。编译器在编译时自动确定 var 代表的类型。如下列代码:

var varInt = 1;
var varStr = "Hello, Vars!";

关闭优化编译,使用 .NET Reflector 反编译得到

int num = 1;
string str = "Hello, Vars!";

该关键字可以在编译时得到类型信息,于是可以进行 IntelliSense 和重构。

类型 object 是所有对象的基类,有着与 dynamic 相似的用法,但是必须进行强制转换才可以用子类的方法、属性等。如:

object objCat = new Cat();
objCat.Meow(); // CS1061 'object' does not contain a definition of 'Meow'
((Cat)objCat).Meow(); // OK

dynamic dynCat = new Cat();
dynCat.Meow(); // OK, but no IntelliSense

dynamic 简化反射

以前我们这样使用反射:

public class DynamicSample
{
public string Name { get; set; }

public int Add(int a, int b)
{
return a + b;
}
}

DynamicSample dynamicSample = new DynamicSample();

var addMethod = typeof(DynamicSample).GetMethod("Add");

//create instance 为了简化演示,这里没有使用反射再创建一个新的对象,而是直接沿用上面的对象 dynamicSample
// 可以发现这里 Invoke 还需要强转返回类型
int re = (int)addMethod.Invoke(dynamicSample, new object[] { 1, 2 });

现在,我们有了简化的写法:

// Invoke 返回的对象是 Object 类型
dynamic dynamicSample2 = constructor.Invoke();
// 这里省略了强转,直接调用
int re2 = dynamicSample2.Add(1, 2);